Программирование

Оператор присваивания

Здесь:

·       := – оператор присваивания;

·       ` ` – оператор блокирования вычисления выражения, находящегося между обратными кавычками. Если между кавычками находится имя переменной, то его вычисление не блокируется;

·       x, x1, x2, …, xn – имена простых переменных (x, delta, …), имена индексированных переменных, то есть элементов массива (w[5], qwe[k, 3], …),  вызовы функций (gg(1), h(t), …);

·       ex, ex1, ex2, …, exn – произвольное выражение, могущее содержать как определенные, так и неопределенные переменные.

        Команда x :=  ex выполняет вычисляемое присваивание. То есть результат вычисления выражения ex при текущих значениях входящих в него переменных приcваивается объекту x;

        Команда x1, x2, …, xn := ex1, ex2, …, exn выполняет вычисляемое присваивание аналогично первому, но здесь результаты вычисления выражений последовательности ex1, ex2, …, exn присваиваются соответствующим элементам последовательности x1, x2, …, xn. Длины последовательностей слева и справа должны быть равными.

        Команды x := `ex` и x1, x2, …, xn := `ex1`, `ex2`, …, `exn` действуют аналогично соответствующим командам, но выполняют невычисляемое присваивание. Иными словами, присваивания реализуются без вычисления соответствующих выражений. Это обеспечивается оператором ` ` блокировки вычислений.

        Команда x1, x2, …, xn := a1, a2, …, a. Здесь отдельные выражения a1, a2, …, an могут быть, как заключены в обратные кавычки, так и нет, форсируя соответственно невычислимые или вычислимые присваивания.

Ветвления

Простые ветвления

 

A.  if  bool

          then stats1

     end if

 

 

 

 

B.  if  bool then stats1
          elif  boo11 then sts1
          

          elif  boolN then stsN

     end if

 

 

 

 

 

 

 

 

·       if (если), then (то), elif (иначе, если), end if  (конец если) – ключевые слова;

·       bool, bool1, …, boolN – логические выражения;

·       stats1, stats2, st1, …, stN – последовательности выражений;

Ветвления с ветвью, выполняемой по умолчанию

 

 

C.  if  bool

         then stats1

         else stats2

     end if

 

D.  if(bool, stats1, stats2)

 

 

E.  if  bool then stats1
          elif  boo11 then sts1
          

          elif  boolN then stsN

          else stats2

     end if

 

 

 

 

 

 

 

 

 

Циклы

 

A.   for par from  expr1 by  expr2 to expr3

             do stats end do

B.   for par from expr1 by expr2 to expr3 while bool

              do stats end do

 

Цикл for-from

 

 

 

 

 

 

for (для), from (от), by (с шагом), to (до), do (выполнять), while (пока), end do (завершить выполнение) – ключевые слова;

Циклы while и do

 

A.  while bool do stats end do

B.  do stats end do

 

       

Здесь: do (выполнять), while (пока), end do (завершение выполнения) – ключевые слова;

 

Цикл for-in

 

A.   for par in obj

             do stats end do

 

 

B.   for par in obj while bool

             do stats end do

 

 

 

 

 

for (для), in (в), while (пока), do (выполнять), end do (завершить выполнение) – ключевые слова;

Примеры:

Ø     for i from 1 to 5 do print(i) od;

Ø     for i from 7/(2+5) to 2+3 do print(i) od:

Ø     for i from 9 to 1 by -2 do print (i) od:

Ø     for i from 1 to 10 by 2 while i<6 do print(i) od:

Ø     for i in [1,2.5.-1,7.12] while i>0 do print(i) od:

 

A.  try stats1

      catch stats2

           finally stats3

      end try

 

Контролируемое выполнение предложений

try (попытка), catch (ловушка), finally (финальный досчет), end try (завершит попытку) – ключевые слова;

 

 

Операторы пропуска и прерывания

Иногда бывает нужным пропустить определенное значение переменной цикла. Для этого используется оператор next (следующий). Приведенный ниже пример иллюстрирует применение оператора next в составе выражения if-fi для исключения вывода значения i = -2:

Ø     for 1 in [1,2,3,-2,4] do if i=2 then next else print(i) fi od:

Другой оператор — break — прерывает выполнение фрагмента программы (или цикла). Его действие поясняет слегка модифицированный предшествующий пример: 

Ø     for i in [1,2,3,-2,4] do if i=2 then break else print(i) fi od:

Процедуры

        Вычисление значений выражений при тех или иных наборах входящих в них переменных можно осуществлять, не используя понятия процедуры. Делается это приблизительно так, как показано в примере 1.

        Пример 1. Вычисление выражения zz при различных значениях входящих в него переменных. В последнем случае демонстрируется тот факт, что в выражение подставляются значения входящих в него переменных, вычисленные до последнего уровня вложенности.

 

> 

restart:

{¿}

> 

zz := x^2+y^2:

{¿}

> 

x := 5: y := 3: zz;

{¿}

> 

x := 1: y := b: zz;

{¿}

> 

x := cos(t): y := sin(t): zz;

{¿}

> 

simplify(%);

{¿}

> 

t := a: a := b: b :=c: zz;

{¿}

Формы записи процедуры для вычисления выражения x2+y2

 

A.  zz := proc(x, y) x^2+y^2 end proc

B.  proc(x, y) x^2+y^2 end proc

 

 

C.  zz := (x, y) -> x^2+y^2

D. (x, y) -> x^2+y^2

       

 

 

 

Вычисления, выражений по схеме примера 1, допустимы, но не всегда удобны. Гораздо практичней использовать для этих целей процедуры специально оформленные именованные или неименованные последовательности Maple-выражений или предложений. До обстоятельного разговора об общей структуре процедур рассмотрим их синтаксис и семантику на конкретных примерах AD, где: zz – имя процедуры; proc, end – ключевые слова; -> – функциональный оператор; x, y – формальные параметры или, по-другому, аргументы процедуры; x^2+y^2 – тело процедуры. Когда в C или D только один аргумент, то заключать его в скобки не обязательно.

        По отношению к AD принята следующая терминология: A – именованная процедура, B – неименованная процедура, C – именованная процедура в функциональном виде, D – неименованная процедура в функциональном виде. В общем случае тело в процедурах типа A и B может быть выражением, последовательностью выражений, предложением или последовательностью предложений. Тело в процедурах типа C и D может быть выражением, последовательностью выражений или структурой if.

        Структуры A и C, являющиеся определениями конкретных именованных процедур, активизируются только после их вычисления (запуска). При этом во время вычисления процедуры ее тело не выполняется. Реальные вычисления по процедуре производятся только после обращения к ней. 

        Неименованные процедуры типа B и D применяются редко, ибо по ним можно проводить лишь разовые вычисления.

Вычисления по процедурам AD предыдущего пункта

a.  zz(a, b)

b.  proc(x, y) x^2+y^2 end proc(a, b)

 

g.  zz (a, b)

d. ((x, y) -> x^2+y^2)(a, b)

       

 

 

Здесь a и b – произвольные выражения. Вычисления проводятся по соответствующим процедурам AD предыдущего пункта при x = a и y = b.

        a, g – вычисления по именованным процедурам типа A и С. Предварительно сами процедуры A и С должны быть вычислены, то есть, запущены на выполнение.

        b, d – вычисления по неименованным процедурам типа B и D. Предварительного вычисления процедур B и D не требуется. Неименованные процедуры часто используются в сочетании с функциями map или map2.

        Примеры 2. Вычисления по неименованным процедурам.

> 

restart:

{¿}

> 

((x, y) -> x^2+y^2)(5, 3);

{¿}

> 

((x, y) -> x^2+y^2)(1, b);

{¿}

> 

map((x, y) -> x^2+y^2, [5, 1, 7], b);

{¿}

> 

proc(x, y) x^2+y^2 end proc(sin(t), cos(t))

{¿}

        Намного удобней работать с именованными процедурами. Создав подобную процедуру, мы можем обращаться к ней без повторения ее записи с любым допустимым набором аргументов и сколько угодно раз.

        Примеры 3. Вычисления по именованным процедурам.

> 

restart:

{¿}

> 

g := (x, y) -> x^2+y^2;

{¿}

> 

g(5, 3);

{¿}

> 

g(5, 3)+g(1, b);

{¿}

> 

f := proc(x, y) x^2+y^2 end proc;

{¿}

> 

f(a, b)+f(3, 4)/f(x, y);

{¿}

        Еще раз подчеркнем, что функциональная форма записи процедуры используется только в том случае, когда ее тело состоит из одного выражения, последовательности выражений или структуры if. Иными словами в такой процедуре не может быть символов “:” или “;”, которые являются разделителями Maple-предложений. В то же время, общая форма записи процедуры с ключевыми словами proc end proc допускает использование любого количества предложений в ее теле.

        Примеры 4. В первых двух примерах представлены процедуры в функциональной форме записи с телами, являющимися одиночными выражениями. В третьем примере приведена процедура в функциональной форме записи с телом, являющимся последовательностью выражений. В последнем примере используется общая форма записи процедуры с телом из трех предложений.

> 

restart:

{¿}

> 

qwe := x -> x^3+x+1:

{Shift+¿}

 

qwe(0), qwe(1), qwe(2);

{¿}

> 

as := (x, y) -> [x+y, x-y, x*y, x/y]:

{Shift+¿}

 

as(a, 1);

{¿}

> 

zxc:= (a, b, c) -> (a, b, c):

{Shift+¿}

 

zxc(a+b, z, 1);

{¿}

> 

uu := proc(k, s)

{Shift+¿}

 

   a := 1: b := 3: c := a*k+b*s:

{Shift+¿}

 

end proc

{¿}

> 

uu(1, 2);

{¿}

Вывод информации о процедуре как объекте

        Большая часть объектов Maple вычисляется рекурсивно до последнего уровня вложенности. Однако имеются и такие объекты, которые вычисляются только до своего имени. К ним относятся процедуры, модули и таблицы (векторы, матрицы и массивы, но не таблицы типа rtable). То есть запуск на выполнение имени процедуры приводит к выводу только этого имени. Форсировать вывод полного текста пользовательских и библиотечных процедур можно функциями eval или print. Вывод листинга пользовательских и библиотечных процедур или их фрагментов в структурированном виде с нумерацией последовательных предложений реализуется  функцией showstat. Такой вывод может быть полезен при отладке процедур. Вывод листинга встроенных процедур невозможен.

        Функция  showstat(proc) выводит листинг пользовательской или библиотечной процедуры proc с нумерацией отдельных предложений ее тела. При этом в листинг не включаются все комментарии и разделы option и description. Встроенные процедуры по eval не выводятся.

        Функция interface(verboseproc = n) позволяет установить значение n (n Î {0, 1, 2, 3}) интерфейсной переменной verboseproc, управляющей типом вывода пользовательских, библиотечных и встроенных процедур. Чем больше n, тем больше информации выводится о процедуре. По умолчанию n = 1.  

        Функция interface(verboseproc) выводит текущее значение интерфейсной переменной verboseproc.

        Функция eval(proc) или print(proc). Если verboseproc = 0, то для любой процедуры proc по eval выводится лишь ее шаблон, то есть начальная и конечная строки. При verboseproc = 1 для пользовательской процедуры текст выводится полностью, для библиотечной процедуры (sin, sum, plot, matrix, …) выводится ее шаблон, а для встроенной процедуры (length, factorial, eval, modp, …) выводится шаблон с ее идентификационным номером. При verboseproc = 2 пользовательские и библиотечные процедуры выводятся полностью, а встроенные процедуры – шаблоном с идентификационным номером. Во всех случаях комментарий при выводе опускается.

        Примеры 5. Здесь демонстрируется вывод информации о процедуре proba.

> 

restart:

{¿}

> 

proba := proc(x)

{¿}

 

local a, b, c, d;

 

 

   a := x^2; b := x^3; c := x^4;

 

 

   d:= x+a+b+c

 

 

end proc:

 

> 

proba;

{¿}

> 

eval(proba);

{¿}

> 

print(proba);

{¿}

> 

showstat(proba);

{¿}

> 

eval(sin);

{¿}

> 

interface(verboseproc = 2):

{Shift+¿}

 

eval(sin);

{¿}

 

eval(length);

{¿}

Локальные и глобальные переменные

        Область действия переменных, а также процедур и модулей, – чрезвычайно важная тема. В языке символьного программирования Maple в этом вопросе имеются значительные отличия от традиционных языков программирования. Прежде чем говорить о возможных типах переменных отметим, что процедуры могут вкладываться друг в друга.

        К локальным переменным процедуры относятся:

·       ее формальные параметры (аргументы);

·       переменные, явно объявленые опцией local;

·       переменные, которым впервые в теле процедуры без предварительного объявления присваиваются конкретные значения;

·       переменные, используемые для организации циклов (итераторы).

        Никакой связи между значениями локальных переменных процедуры и одноименных переменных, находящихся вне процедуры, не имеется. В то же время для процедур, вложенных в данную процедуру, локальные переменные доступны, но вычисляются в них лишь до первого уровня вложенности. Например, если x –локальная переменная, то после присваиваний x := y: y := z выражение x+5 будет иметь значение, равное y+5 (но не z+5!). Если требуется, чтобы вычисления локальных переменных проводились до последней глубины вложенности, необходимо использовать функцию eval.

        К глобальным переменным процедуры относятся те ее переменные, которые не являются локальными и, или инициализированы на верхнем уровне (в документе), или объявлены опцией global в процедуре или в любых других процедурах, независимых от данной, содержащихся в ней или содержащих ее. В любом случае глобальные переменные вычисляются до последнего уровня вложенности.

        Пример 6. В процедуре qw переменная y явно, опцией local, и переменная x неявно, присваиванием значения, объявлены локальными. В примере демонстрируется отсутствие связи между локальными переменными qw и одноименными переменными, находящимися вне qw.

 

> 

restart:

{¿}

> 

x := 100: x, y;

{¿}

> 

qw := proc(t)

{Shift+¿}

 

   local y;

{Shift+¿}

 

   x := 1; y := x+1+t;

{Shift+¿}

 

   x, y;

{Shift+¿}

 

end proc:

{¿}

> 

qw(0);

{¿}

> 

x, y;

{¿}

        Примеры 7. Здесь в процедуре строкой local t, a, b, c объявлены четыре локальные переменные t, a, b и c Прямые обращения к процедуре test в виде test(d) демонстрируют, что ее локальные переменные вычисляются лишь до первого уровня вложенности. Обращение к этой же процедуре в виде eval(test(d)) форсируют вычисление ее локальных переменных до последнего уровня вложенности. Аналогичные вычисления реализуются и без использования процедуры.

 

> 

restart:

{¿}

> 

test := proc(x)

{Shift+¿}

 

   local t, a, b, c;

{Shift+¿}

 

   t := a: a := b: b :=c:

{Shift+¿}

 

   sin(t)+a+b+x^3;

{Shift+¿}

 

end proc:

{¿}

> 

test(d);

{¿}

> 

eval(test(d))

{¿}

> 

t := a: a := b: b := c:

{Shift+¿}

 

sin(t)+a+b+d^3;

{¿}

        Пример 8. Здесь демонстрируется вычисление в процедуре da глобальной переменной g до последнего уровня вложенности и локальной переменной a до первого уровня вложенности.

> 

restart:

{¿}

> 

da := proc(x)

{Shift+¿}

 

   local a, b, c, d, e, f, h;

{Shift+¿}

 

   global g;

{Shift+¿}

 

   a := b: b := c: c := d:

{Shift+¿}

 

   g := e: e := f: f := h:

{Shift+¿}

 

   x+a+g

{Shift+¿}

 

end proc:

{¿}

 

 

 

> 

da(0);

{¿}

        Пример 9. После выполнения процедуры pr и запуска первой выполняемой группы “> x, y” неопределенными оказались не только локальная переменная x, но и глобальная переменная y. И это естественно, ибо ни одного обращения к процедуре pr до этого момента не производилось. Результат выполнения следующей выполняемой группы “> pr(1), x, y” подтвердил, что значение глобальной переменной у доступно в документе и вне процедуры pr. Заметим, что вне pr доступно также и глобальное имя процедуры proo. То есть после обращения к pr можно проводить вычисления и по ее внутренней процедуре proo.

 

> 

restart:

{Shift+¿}

 

pr := proc(a)

{Shift+¿}

 

   local x; global y, proo;

{Shift+¿}

 

   x := a: y := 500;

{Shift+¿}

 

   proo := proc(t) x+y+t end proc;

{Shift+¿}

 

   proo(w);

{Shift+¿}

 

end proc:

{¿}

> 

x, y;

{¿}

> 

pr(1), x, y;

{¿}

> 

proo(2);

{¿}

Структура процедуры

        Часто большую задачу решают разбиением ее на серию локальных подзадач. Для каждой подзадачи создается процедура ее решения. Совокупность обращений к этим процедурам, необходимых для полного решения исходной задачи, оформляется в виде основной (головной, запускающей) программы. Подобный блочный подход лежит в основе парадигмы структурного программирования, а развитый механизм процедур Maple-языка во многих отношениях способствует его реализации. Структурное программирование позволяет повысить эффективность, прозрачность и модифицируемость создаваемых программных средств, уменьшить время их разработки. Но прежде чем решать конкретные задачи, нам предстоит ознакомиться с синтаксисом и семантикой процедуры в самом общем виде, чем и займемся. 

В общем случае процедура записывается сле­дующим образом:

 

pro := proc(argums)

#

pro – имя процедуры;

 

description str;

#

str – строки описание процедуры;

 

local lvars;

#

lvarsимена локальных переменных;

 

global gvars;

#

gvarsимена глобальных переменных;

 

option opts;

#

optsпоследовательность опций;

body

#

bodyпоследовательность предложений;

end proc

#

конечная строка процедуры.

Рис. 1. Структура процедуры

        На рисунке 1 после символов комментария # приведены краткие пояснения для отдельных разделов процедуры. Ключевые слова description, local, global и option являются соответственно заголовками разделов описания, объявления локальных переменных, объявления глобальных переменных и задания опций процедуры. Опции процедуры, уточняют ее свойства. Раздел body называется телом процедуры и представляет собой последовательность Maple-предложений, по которым реализуются вычисления. Любой из разделов не является обязательным. Раздел может занимать несколько строк документа, или, наоборот, несколько разделов размещаться в одной его строке. Разделы description, local, global и option можно записывать в произвольном порядке, но все они должны предшествовать первому предложению тела body. Дадим более подробное описание элементов процедуры:

·       proc, description, local, global, option, end ключевые слова;

·       pro := proc(argums) – строка заголовка процедуры;

·       argums – последовательность аргументов или, по-другому, формальных параметров процедуры. При обращениях к процедуре ее формальные параметры получают конкретные (фактические) значения;

·       end proc (или просто end) – завершающая строка процедуры;

·       description str – раздел (секция) описания процедуры. Параметр str может быть задан литералом без пробелов, строкой или последовательностью строк. Описание служит комментарием для процедуры и на ее выполнение никакого влияния не оказывает;

·       local lseq – раздел объявления имен локальных простых или типизированных переменных. Имена задаются элементами последовательности lseq. Инициализация локальных переменных и (или) вычисления с их участием должны проходить в теле процедуры. Элементы lseq являются локальными в том смысле, что их значения, вообще говоря, недоступны за пределами процедуры. Но сделать их доступными все же можно (см. пример 10);

·       global gseq – раздел объявления имен глобальных переменных. Имена задаются элементами последовательности gseq, которые доступны как в процедуре, так и вне ее;

·       option optsнеобязательные объявления последовательности opts опций модуля, определяющих дополнительные характеристики процедуры. Подробно опции описываются в следующем разделе. Здесь же кратко указывается их назначение:

o       copyright – уведомление о правах разработчиков процедуры;

o       trace трассирование выполнения процедуры;

o       inline объявление подставляемой процедуры;

o       builtin – объявление встроенной процедуры;

o       remember – привязка к процедуре таблицы ее значений;

o       cache – привязка к процедуре хэш-таблицы ее значений;

o       overload объявление процедуры перегружаемой;

o       system – объявление системной процедуры;

o       operator – вывод процедуры в функциональной форме;

o       arrow – блокирование упрощений;

o       call_externall = name – объявление внешней процедуры.

·       body – последовательность из нуля или более Maple-предложений, составляющих тело процедуры. Эти предложения используются для организации вычислений при обращениях к процедуре.

        Именованные процедуры активизируются только после их вычисления, то есть запуска. При этом во время вычисления процедуры ее тело не выполняется. Реальные вычисления по процедуре производятся только после обращения к ней.

        Синтаксис процедуры во многом напоминает синтаксис модуля. И процедуры и модули могут содержаться друг в друге при любой глубине вложенности. Процедуры, подобно любым другим выражениям, можно размещать в репозитарии, то есть в специальной библиотечной структуре.

        Пример 10. Написать процедуру для вычисления периметра треугольника, заданного на плоскости координатами своих вершин в виде списка списков [[x1, y1], [x2, y2], [x3, y3]], где x1, y1, x2, y2, x3, y3 – целые числа. 

        Решение. Ниже представлен возможный вариант требуемой процедуры, названный triangle. Дадим ее описание.

·       в качестве единственного аргумента triangle принимает список li. Указание типа данных для li в виде li::listlist(integer) гарантирует, что будут приниматься не любые списки, а только списки списков у которых все внутренние списки имеют одинаковую длину (listlist), а их элементами являются целые числа (numeric);

·       процедура triangle содержит в себе две локальные процедуры dis и lineQ. Никаких ошибок при вычислениях в dis или lineQ возникнуть не может, ибо мы обращаемся к ним только из процедуры triangle и всегда с правильными фактическими значениями параметров. Вне triangle процедуры dis и lineQ не определены;

·       по локальной процедуре lineQ проверяется невырожденность треугольника. Для этого из полученного списка списков вершин li вида [[x1, y1], [x2, y2], [x3, y3]] создается матрица

 

 

и вычисляется ее определитель. При det ¹ 0 lineQ возвращает true, иначе – false.

·       по локальной процедуре dis вычисляются расстояния между точками A и B плоскости по их координатам. Точки A и B подаются в dis в виде двух простых списков с двумя числовыми элементами в каждом.   

·       вычисления по triangle начинаются с дополнительного исследования полученных фактических значений. Проверяется, действительно ли входной список имеет три элемента (nops(li) = 3) и является ли каждый из них списком из двух элементов (nops(li[1]) = 2). Если это не так, то выводится сообщение "That are not 3 points" (это не три точки) и вычисления прерываются. В противном случае реализуется проверка по lineQ. Для вырожденного треугольника выводится сообщение "That is a singular triangle" и вычисления прерываются, а иначе по dis вычисляются и суммируются длины всех сторон треугольника. Именно эта сумма (периметр) и возвращается из процедуры потому, что она является последним вычисленным в triangle значением.

> 

restart:

{¿}

> 

triangle := proc(li::listlist(integer))

{Shift+¿}

 

   local s, t, r, dis, lineQ;

{Shift+¿}

 

   description "Perimeter of a triangle";

{Shift+¿}

 

   #

{Shift+¿}

 

   dis := proc(A, B)   # расстояние между A и B

{Shift+¿}

 

      evalf(sqrt((A[1]-B[1])^2+(A[2]-B[2])^2))

{Shift+¿}

 

   end proc;

{Shift+¿}

 

   #

{Shift+¿}

 

   lineQ := proc(li)  # вырожден ли треугольник?

{Shift+¿}

 

     Matrix([[op(li[1]), 1], [op(li[2]), 1], [op(li[3]), 1]]);

{Shift+¿}

 

     LinearAlgebra[Determinant](%);

{Shift+¿}

 

     evalb(% <> 0)

{Shift+¿}

 

  end proc:

{Shift+¿}

 

   #

{Shift+¿}

 

   if  nops(li) = 3 and nops(li[1]) = 2 then

{Shift+¿}

 

      if  lineQ(li) then

{Shift+¿}

 

         s := li[1]: t := li[2]: r := li[3];

{Shift+¿}

 

         dis(s, t)+dis(s, r)+dis(t, r);

{Shift+¿}

 

      else

{Shift+¿}

 

         print("That is a singular triangle")

{Shift+¿}

 

      end if

{Shift+¿}

 

   else

{Shift+¿}

 

      print("That are not 3 points")

{Shift+¿}

 

   end if

{Shift+¿}

 

end proc:

{¿}

> 

li := [[1, 2], [5, 1], [6, 0]]: triangle(li);

{¿}

> 

li := [[1, 2], [5, 1], [zz, 0]]: triangle(li);

{¿}

> 

li := [[1, 1], [2, 2], [5, 5]]: triangle(li);

{¿}

> 

li := [[1, 1], [2, 2], [15, 0], [8, 9]]: triangle(li);

{¿}

        Пример 11. Здесь демонстрируется способ доступа к локальным объектам процедуры. В какой-то степени – это аналог экспорта членов модуля. Пусть строка dis(s, t)+dis(s, r)+dis(t, r) в процедуре triangle предыдущего при­мера заменена последовательностью dis(s, t)+dis(s, r)+dis(t, r), dis, lineQ, s, t, r и tria – имя вновь полученной процедуры. При правильных обращениях к tria последней всегда вычисляется указанная последовательность. Поэтому ее значение и будет возвращаться. Данное обстоятельство может быть использовано для доступа к локальным  объектам tria. Например, можно проводить вычисления по процедурам lineQ и dis вне tria, хотя для этих целей они не создавались. Делается это так:

 

restart:

{Shift+¿}

 

tria := proc(li::listlist(integer))

{¿}

> 

w := tria([[9, 2], [5, 1], [6, 0]]);

{¿}

> 

w[2]([7, 2], [5, 8]);                # вычисления по dis

{¿}

> 

w[3]([[9, 2], [5, 1], [6, 0]]);   # вычисления по lineQ

{¿}

> 

w[4], w[5]+w[6];

{¿}

Получение информации о процедуре

        Команда op(k, eval(pro)). В общем случае процедура имеет восемь компонентов (операндов, элементов). Любой из них может отсутствовать. Команда op при k = 1, 2, …, 8 выводит следующие компоненты процедуры pro:

·       k = 1. args  – последовательность аргументов, то есть формальных параметров;

·       k = 2. последовательность lvars имен локальных переменных;

·       k = 3. последовательность opts опций;

·       k = 4. таблицу remember;

·       k = 5. описание процедуры;

·       k = 6. последовательность gvars имен глобальных переменных;

·       k = 7. лексическую таблицу;

·       k = 8. Тип return (если присутствует). ???

        Команда op(eval(pro)) выводит последовательность из всех восьми компонентов процедуры pro.

        Команда nops(eval(pro)) возвращает количество компонентов процедуры pro, то есть число 8.

        Переменная procname является системной. Она имеет значение, равное имени выполняемой процедуры. Переменную D в виде 'procname(args)' можно использовать при обработке исключительных ситуаций для вывода обращения к процедуре или в виде procname(…) для рекурсивных обращений в процедурах.

        Примеры 12. Здесь в первом примере показывается вывод компонентов процедуры triangle из примера 10. Во втором примере на рекурсивной процедуре вычисления факториала целого неотрицательного числа демонстрируется использование переменной procname.

> 

op(1, eval(triangle));                  # пример 1

{¿}

> 

op(2, eval(triangle));

{¿}

> 

op(5, eval(triangle));

{¿}

> 

op(eval(triangle));

{¿}

> 

nops(eval(triangle));

{¿}

> 

fact := proc(n::nonnegative)       # пример 2

{Shift+¿}

 

  if n = 0 then 1

{Shift+¿}

 

  elif n <= 100 then n*procname(n-1)

{Shift+¿}

 

  else  'procname(args)'

{Shift+¿}

 

  end if

{Shift+¿}

 

end proc:

{¿}

> 

fact(0), fact(1), fact(10);

{¿}

> 

fact(101);

{¿}

Параметры процедуры

Формальные и фактические аргументы    

        Выражение na := proc(x1::t1, x2::t2, …, xn::tn) body end proc представляет собой общую форму записи процедуры с именем na и телом body. Отдельные элементы последовательности x1::t1, x2::t2, …, xn::tn задаются именами переменных x1, x2, …, xn  и соответствующими им типами t1, t2, …, tn. Их называют формальными аргументами или формальными параметрами. Типами tk в элементах xk::tk (k = 1, 2, …, n) могут быть любые типы языка Maple, тестируемые функциями type и whattype. Элемент tk вместе со знаком типирования :: задавать не обязательно. Это соответствует заданию типа anything – любой тип.  Подчеркнем, что в A формальные аргументы указываются явно, то есть n ³ 1.

        Выражение na(expr1, expr2, …, exprm) представляет собой обращение к процедуре na или, по-другому, ее вызов. Отдельные элементы последовательности expr1, expr2, …, exprm являются произвольными выражениями. Их называют фактическими аргументами или фактическими параметрами. При обращении к процедуре na в случае m = n происходит следующее. Если тип exprk есть tk при всех k = 1, 2, …, n, то каждой переменной xk формального параметра xk::tk присваивается соответствующий ему фактический параметр exprk. После этого начинает выполняться тело процедуры body. Если же тип exprk не есть tk хотя бы при одном k, то генерируется и выводится сообщение об ошибке и процедура завершает свою работу.

        Уточним правила вычисления и передачи фактических аргументов формальным параметрам. В большей части процедур это делается стандартным способом, что означает следующее:

·       если вызов процедуры проводится на верхнем уровне, то ее фактические аргументы передаются полностью вычисленными до последнего уровня вложенности;

·       если вызов процедуры реализуется внутри другой процедуры, то при вычислении ее фактических аргументов глобальные переменные вычисляются до последнего уровня вложенности, а локальные переменные – только на один уровень вложенности;

·       фактические аргументы перед передачей их значений формальным параметрам вычисляются в произвольном (непредсказуемом) порядке;

        Существуют встроенные процедуры, использующие специальный механизм передачи и вычисления фактических аргументов. Вот некоторые из них: add, mul, seq, assigned, eval, evaln, if, time, timelimit. Подобный механизм может быть инициирован и у пользовательских процедур. Достигается это объявлением типов uneval или evaln для одного или нескольких формальных параметров. Тип uneval предписывает передавать фактические параметры невычисленными, а тип evaln – вычисленными только до имени. Заметим, что команды map и map2 не работают с процедурами, использующими специальные правила вычисления фактических аргументов.

         Выражение na := proc() body end proc представляет собой форму записи процедуры с именем na и телом body. При таком синтаксисе процедуры ее формальные аргументы отсутствуют, но обращаться к ней можно с переменным количеством фактических аргументов. При этом в теле процедуры доступны системные переменные nargs и args. Значением первой из них является реальное количество фактических параметров при данном обращении к процедуре, а второй – последовательность фактических параметров при этом обращении.

        Примеры 13. Здесь демонстрируются примеры записи формальных аргументов в процедурах.

> 

qw1 := proc(x::integer, y::integer) … end proc: 

{¿}

> 

qw2 := proc(x::integer[1..5]) … end proc:     

{¿}

> 

qw3 := proc(x::even, y::odd) … end proc:                  

{¿}

> 

qw4 := proc(x::posint) … end proc:

{¿}

> 

qw5 := proc(st::string, q1, q2) … end proc:

{¿}

> 

qw6 := proc(a::character) … end proc:         

{¿}

> 

qw7 := proc(li::list, se::set, ttt) … end proc:         

{¿}

> 

qw8 := proc(li::listlist) … end proc:         

{¿}

> 

qw9 := proc(li::list(integer)) … end proc:         

        {¿}

        Дадим краткие пояснения к типам формальных аргументов в процедурах  qw1 – qw9:

        x::integer, y::integer. x и y целые;

        x::integer[1..5]. xцелое из диапазона 1..5;

        x::even, y::odd. xцелое четное, yцелое нечетное;

        x::posint. xцелое положительное (натуральное);

        st::string, q1, q2. xтипа строки, q1 и q2 – произвольного типа;

        a::character. aлитера (знак);

        li::list, se::set, ttt. liсписок, seмножество, tttпроизвольного типа;

        li::listlist. liсписок списков;

        li::list(integer). liсписок, элементы которого целые числа.

        Пример 14. Здесь демонстрируются обращения к процедуре.

> 

restart:

{¿}

> 

powe := proc(n::posint, m::nonnegint, x::integer[1..3])

{Shift+¿}

 

   (n^m)^x

{Shift+¿}

 

end proc:     

{¿}

> 

powe(5, 7, 3);

{¿}

> 

powe(a, 7, 3);                            # ошибка, aposint ?

{¿}

> 

powe(5, -1, 1);                   # ошибка, -1 – nonnegint ?

{¿}

> 

powe(5, -1, 4);                 # ошибка, 4 – integer[1..3] ?

       {¿}

        Взаимоотношения формальных и фактических аргументов при обращениях к процедуре могут отличаться от описанных выше. Если процедура A вызывается по B при m > n, то при вычислениях используются только первые n элементов последовательности expr1, expr2, …, exprm. Если такой вызов происходит при m < n, то возможны варианты. Процедура выполняется, когда типы exprk равны tk (k = 1, 2, …, m) и в данном вызове в вычислениях не участвуют переменные xm+1, xm+2, …, xn. В противном случае, как только в body к вычислениям привлекается одна из переменных xm+1, xm+2, …, xn, генерируется и выводится сообщение об ошибке.

Возвращаемые процедурой значения

        Мы привыкли считать, что при вызове процедура или возвращает последнее вычисленное в ней значение, или формирует и возвращает сообщение о той или иной возникшей исключительной ситуации. Однако инициировать возврат значений в процедуре можно и иными способами: командой return, командой error, с помощью параметров-переменных и с помощью глобальных переменных. Поговорим об этом подробнее.

        Команда return expr может находиться только внутри процедур и модулей, в том числе и в их конечных или бесконечных циклах. По команде return выполнение процедуры прекращается и возвращается значение выражения expr. Параметр expr может иметь любой тип, быть последовательностью выражений или вообще отсутствовать. Особенно полезным оказывается использование команды при наличии в процедуре нескольких естественных точек выхода. Иногда удается переписать процедуры так, что необходимость в досрочном выходе из них по return отпадает.

        Пример 20. Выяснить, пройдет ли кирпич с ребрами a, b и c в прямоугольное отверстие со сторонами X и Y, где x, y, a, b, c – положительные числа. Просовывать кирпич в отверстие можно только так, чтобы все его ребра были параллельны или перпендикулярны каждой из сторон отверстия.

        Решение. Одно из возможных решений данной задачи реализуется приведенной ниже функцией brick. Сначала локальным переменным x и y присваиваются соответственно значения min(X, Y) и max(X, Y). Далее делается попытка протолкнуть кирпич последовательно по граням с ребрами {ab}, {b, c} и {a, c}. При успехе командой return или функцией `if` возвращается значение true. При неуспехе по `if` возвращается значение false.

> 

restart:

{¿}

> 

brick := proc(X::positive, Y::positive,

{Shift+¿}

 

      a::positive, b::positive, c::positive)

{Shift+¿}

 

   local x, y;

{Shift+¿}

 

   x := min(X, Y): y := max(X, Y):

{Shift+¿}

 

   if min(a, b) <= x and max(a, b) <= y

{Shift+¿}

 

      then return true

{Shift+¿}

 

   end if:

{Shift+¿}

 

   if min(b, c) <= x and max(b, c) <= y

{Shift+¿}

 

      then return true

{Shift+¿}

 

   end if:  

{Shift+¿}

 

   `if`(min(a, c) <= x and max(a, c) <= y, true, false):

{Shift+¿}

 

end proc:

{¿}

> 

brick(20, 30, 20, 40, 29.5);

{¿}

> 

brick(20, 30, 20, 40, 31);

{¿}

        Пример 21. Здесь демонстрируется выход из процедуры с помощью команды return, расположенной в бесконечном цикле. Решается следующая задача. Для неотрицательного числа fro находится наименьший индекс n, при котором , и вычисляется сумма из левой части этого неравенства (частичная сумма расходящегося гармонического ряда). Кроме того, приведен вариант решения поставленной задачи без использования команды return.

> 

restart:

{¿}

> 

er := proc(fro::positive)

{Shift+¿}

 

   local s, k; s := 0:

{Shift+¿}

 

   for k from 1 to infinity

{Shift+¿}

 

      do s := s+1/k:

{Shift+¿}

 

      if s > fro then return k, s end if

{Shift+¿}

 

   end do

{Shift+¿}

 

end proc:

{¿}

> 

er(3), er(4);

{¿}

> 

restart:                 # вариант без return

{¿}

 

err := proc(fro)

{Shift+¿}

 

   local s, k; s := 0:

{Shift+¿}

 

   for k from 1 to infinity while s <= fro 

{Shift+¿}

 

      do s := s+1/k:

{Shift+¿}

 

   end do: k-1, s

{Shift+¿}

 

end proc:

{¿}      

Возврат результатов по error

        Команда error mes, par1, par2, … инициирует возникновение исключительной ситуации. То есть, прекращаются вычисления по процедуре, а также формируется, запоминается и возвращается сообщение об ошибке. Оно состоит из слова Error, выражения (in имя процедуры) и строкового значения параметра mes. В mes могут содержаться шаблоны вида %k, где k – натуральное число. В этом случае в A должна присутствовать последовательность параметров par1, par2, … И перед выводом mes каждый его шаблон %k заменяется вставкой – значением k-го параметра указанной последовательности. Допускаются в mes шаблоны вида %0, что приводит к вставке на их место последовательности значений всех параметров par1, par2, … Если mes вообще не задано, то по A возвращается сообщение, сформированное последней ранее возникшей исключительной ситуацией. Наиболее полезная структура, используемая для управления исключительными ситуациями, – trycatch. Она описана позже.

        Пример 22. Здесь, на примере вычисления площади треугольника по длинам x, y, z трех его сторон, демонстрируется инициирование по error исключительных ситуаций с выводом соответствующих сообщений. Эти ситуации возникают, когда длина какой-либо из сторон треугольника больше суммы длин двух других его сторон. Для вычисления площади s треугольника используется формула Герона:

 

 

> 

restart:

{¿}

> 

tri:=proc(x::numeric, y::numeric, z::numeric)

{Shift+¿}

 

   local p;

{Shift+¿}

 

   if x > y+z then

{Shift+¿}

 

      error "invalid x > y+z: %1 > %2+%3", x, y, z

{Shift+¿}

 

   elif y > x+z then

{Shift+¿}

 

       error "invalid y > x+z: %1 > %2+%3", y, x, z

{Shift+¿}

 

   elif z > x+y then

{Shift+¿}

 

       error "invalid z > x+y: %1 > %2+%3", z, x, y

{Shift+¿}

 

   else p := (x+y+z)/2: sqrt(p*(p-x)*(p-y)*(p-z))

{Shift+¿}

 

   end if

{Shift+¿}

 

end proc:

{¿}

> 

tri(49.1, 90, 33);

{¿}

> 

tri(6,9,7);

{¿}

Вывод предупреждающего сообщения

        Функция WARNING(str, par1, par2, …) выводит предупреждающее сообщение, заданное строкой str, но вычисления по процедуре не прекращаются. Перед выводом str ее шаблоны вида %k, где k – натуральное число или 0, замещаются значениями параметров par1, par2, … точно так, как это делается в команде error.

        Функция intervace(warnlevel = n) устанавливает уровень n (n Î {0, 1, 2, 3, 4}) вывода предупреждающих сообщений. Чем больше n, тем для большего числа объектов разрешается вывод сообщений. По умолчанию n = 3. При n = 0 происходит блокирование вывода любых предупреждающих сообщений.

        Пример 23. Здесь перед вычислением ln(x) проверяется знак x. При
x < 0  сообщение выводится по команде WARNING, а при x = 0 – по команде error.

> 

restart:

{¿}

> 

g := proc(x)

{Shift+¿}

 

   if x < 0

{Shift+¿}

 

      then WARNING("%1 < 0; result is complex", x)

{Shift+¿}

 

   elif x = 0

{Shift+¿}

 

      then error("x = %1", x)

{Shift+¿}

 

   end if:

{Shift+¿}

 

   ln(x)

{Shift+¿}

 

end proc:

{¿}

> 

g(7); evalf(%);

{¿}

> 

g(-2);

{¿}

> 

g(0)

 

Задания:

1. Написать процедуру вычисления корней квадратного уравнения.

2. Написать процедуру вычисления НОД двух чисел.